@file:Suppress("unused")

package xyz.scootaloo.kami.server.controller

import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.file.FileSystemException
import io.vertx.core.net.impl.URIDecoder
import io.vertx.ext.web.Route
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.BodyHandler
import io.vertx.kotlin.core.json.jsonObjectOf
import kotlinx.coroutines.CoroutineScope
import org.slf4j.Logger
import xyz.scootaloo.kami.server.controller.dto.BaseDTO
import xyz.scootaloo.kami.server.controller.dto.SC
import xyz.scootaloo.kami.server.controller.dto.checkPropertiesIntegrity
import xyz.scootaloo.kami.server.controller.dto.messageOf
import xyz.scootaloo.kami.server.controller.handler.attachCorsHandler
import xyz.scootaloo.kami.server.controller.handler.attachJwtHandler
import xyz.scootaloo.kami.server.controller.handler.attachSockJsHandler
import xyz.scootaloo.kami.server.model.AccessType.*
import xyz.scootaloo.kami.server.model.UPKey
import xyz.scootaloo.kami.server.model.UserAccessToken
import xyz.scootaloo.kami.server.model.UserPrincipalKey.*
import xyz.scootaloo.kami.server.standard.*
import xyz.scootaloo.kami.server.verticle.runCoroutineOnWorkerContext
import xyz.scootaloo.kami.server.verticle.vertx
import java.io.FileNotFoundException
import java.nio.file.NoSuchFileException
import java.nio.file.NotDirectoryException
import kotlin.reflect.KClass

typealias BaseController = AbstractHttpRouterConfiguration
typealias CoroutineReqHandler = suspend CoroutineScope.(RoutingContext) -> Unit

// RoutingContext APIs

fun decodeURIComponent(uri: String): String {
    return URIDecoder.decodeURIComponent(uri, false)
}

@Throws(JsonContentIncompleteException::class)
fun <T : BaseDTO> RoutingContext.readBodyAsDTO(type: KClass<T>): T {
    return bodyAsJson.mapTo(type.java).checkPropertiesIntegrity()
}

infix fun RoutingContext.access(path: String) = UserAccessToken(
    uid = safeReadUserProp(UPKey.ID),
    level = safeReadUserProp(UPKey.ROLE),
    path = decodeURIComponent(path)
)

fun RoutingContext.uid(): Int {
    return safeReadUserProp(UPKey.ID)
}

@Suppress("UNCHECKED_CAST")
fun <T> RoutingContext.safeReadUserProp(key: UPKey): T {
    val userProp = user()?.principal() ?: jsonObjectOf()
    return when (key) {
        ID -> userProp.safeGetInteger(key.key, key.defValue as Int)
        USERNAME -> userProp.safeGetString(key.key, key.defValue as String)
        ROLE -> userProp.safeGetInteger(key.key, key.defValue as Int)
    } as T
}

fun RoutingContext.rest(data: Any? = null, state: SC = SC.SUCCEED) {
    if (this.response().ended())
        return
    json(messageOf(data, state))
}

fun RoutingContext.restWithCode(state: SC) = rest(null, state)

fun Route.coroutineHandler(block: CoroutineReqHandler, log: Logger): Route {
    return this.blockingHandler({ ctx ->
        ctx.coroutineReqHandle(log, block)
    }, false)
}

fun RoutingContext.coroutineReqHandle(log: Logger, block: CoroutineReqHandler) {
    val ctx = this
    runCoroutineOnWorkerContext {
        try {
            block(ctx)
        } catch (ex: Throwable) {
            if (!ctx.response().ended()) {
                ctx.restWithCode(matchingExceptionCode(ex, log))
            }
        }
    }
}

fun jsonIncomplete() = JsonContentIncompleteException()

private fun matchingExceptionCode(error: Throwable, log: Logger): SC {
    val innerError = unwrapException(error)
    val hasProcessed = log.internalErrorHandle(innerError)
    return when (innerError) {
        is FileNotFoundException -> SC.FILE_NOT_FOUND
        is NoSuchFileException -> SC.FILE_NOT_EXISTS
        is NotDirectoryException -> SC.NOT_DIRECTORY
        is FileAlreadyExistsException -> SC.FILE_ALREADY_EXISTS
        is JsonContentIncompleteException -> SC.INFO_INCOMPLETE
        is CacheInvalidException -> SC.TOKEN_EXPIRED
        is UserExistsException -> SC.USERNAME_DUPLICATE
        is UserPasswordMistakeException -> SC.ACCOUNT_MATCHING_FAILURE
        is UserNotExistsException -> SC.USERNAME_NOT_EXISTS
        is AccessViolationException -> SC.ACCESS_OUT_OF_BOUND
        is RequestFormDataIncompleteException -> SC.FORM_DATA_INCOMPLETE
        is AccessDeniedException -> matchAccessDeniedCode(innerError)
        else -> {
            if (!hasProcessed)
                log.error("未定义的异常", innerError)
            SC.FAILURE
        }
    }
}

private fun unwrapException(wrapped: Throwable): Throwable {
    return if (wrapped is FileSystemException) {
        wrapped.cause ?: wrapped
    } else {
        wrapped
    }
}

private fun matchAccessDeniedCode(e: AccessDeniedException): SC {
    return when (e.type) {
        ACCESS -> SC.NO_ACCESS_PERMISSION
        MODIFY -> SC.NO_CREATION_PERMISSION
        DOWNLOAD -> SC.NO_DOWNLOAD_PERMISSION
        else -> SC.NO_DEFINE_PERMISSION
    }
}

/**
 * 模仿SpringMVC的设计, 把处理路由的相关逻辑放在一个类中,
 * 而[AbstractHttpRouterConfiguration]是这些类的公共超类, 提供了一些便捷的api, 优化一些模板代码
 *
 * > 别名 [BaseController]
 *
 * @author flutterdash@qq.com
 * @since 2021/12/31 17:29
 */
abstract class AbstractHttpRouterConfiguration {

    open val enableJwt = false
    open val enableCors = true

    abstract val log: Logger

    /**
     * @return 子路由的挂载点
     */
    abstract val mountPoint: String

    /**
     * 实现路由逻辑
     *
     * @param router 路由对象
     */
    abstract fun doRoute(router: Router): Router

    // Router APIs

    fun Router.get(path: String, handler: CoroutineReqHandler) = get(path).coroutineHandler(handler, log)
    fun Router.put(path: String, handler: CoroutineReqHandler) = put(path).coroutineHandler(handler, log)
    fun Router.post(path: String, handler: CoroutineReqHandler) = post(path).coroutineHandler(handler, log)
    fun Router.trace(path: String, handler: CoroutineReqHandler) = trace(path).coroutineHandler(handler, log)
    fun Router.patch(path: String, handler: CoroutineReqHandler) = patch(path).coroutineHandler(handler, log)
    fun Router.delete(path: String, handler: CoroutineReqHandler) = delete(path).coroutineHandler(handler, log)
    fun Router.options(path: String, handler: CoroutineReqHandler) = options(path).coroutineHandler(handler, log)

    fun Router.regex(pattern: String, handler: Handler<RoutingContext>) = // unused yet
        routeWithRegex(pattern).handler(handler)!!

    companion object {
        /**
         * 应用http处理逻辑注册中心
         *
         * 在这里将各个Controller按顺序注册到系统
         */
        fun bindRouters(vertx: Vertx, rootRouter: Router) {
            rootRouter.route().handler(
                BodyHandler.create()
                    .setMergeFormAttributes(true)
                    .setHandleFileUploads(true)
                    .setDeleteUploadedFilesOnEnd(true)
            )

            attachSockJsHandler("/eventbus", rootRouter)

            for (router in routers) {
                if (router.enableCors)
                    attachCorsHandler(router.mountPoint, rootRouter)
                if (router.enableJwt)
                    attachJwtHandler(router.mountPoint, rootRouter)

                val subRouter = Router.router(vertx)
                rootRouter.mountSubRouter(router.mountPoint, router.doRoute(subRouter))
            }

            registerErrorHandler(rootRouter)
        }

        private fun registerErrorHandler(router: Router) {
            router.errorHandler(500) { ctx ->
                if (!ctx.response().ended()) {
                    ctx.restWithCode(SC.SERVER_ERROR)
                }
            }
            vertx.orCreateContext.exceptionHandler { throwable ->
                STATIC_LOG.warn("未捕获的异常 => ${throwable.message}")
            }
        }

        private val routers = listOf(
            HeartbeatController,
            AccountController,
            ServerCtrlController,
            FileViewController,
            DownloadController,
            UploadController,
            StaticAssetsController
        )
    }
}